Visualização de Dados: Do Caos à Clareza
Tutorial Completo com Datasets Financeiros Reais do Kaggle
🎯 Objetivo da Apresentação
- Mostrar como NÃO fazer visualizações
- Corrigir passo a passo aplicando boas práticas
- Comparar implementação em Python e R (rodando dentro de Julia!)
- Entender quando usar cada tipo de gráfico
- Análise completa de portfolio financeiro
Dataset Kaggle utilizado:
📦 Setup Inicial
#| echo: true
#| output: false
#| warning: false
using Pkg
# Instalar pacotes necessários (descomentar na primeira vez)
Pkg.add(["CSV", "DataFrames", "Plots", "StatsPlots", "Statistics", "Distributions", "GLM", "Dates", "StatsBase", "PyCall", "RCall"])
using CSV, DataFrames, Plots, StatsPlots, Statistics
using Distributions, GLM, Dates, StatsBase
using PyCall, RCall
# Configurar Python e R
ENV["PYTHON"] = "" # usar conda python
Pkg.build("PyCall")
println("✅ Pacotes carregados com sucesso!")📊 Tipos de Variáveis → Tipos de Gráficos
| Variáveis | Gráfico Recomendado | Quando Usar |
|---|---|---|
| 1 Quantitativa | Histograma, Densidade, Boxplot | Distribuição |
| 1 Categórica | Barras | Frequências |
| 2 Quantitativas | Scatter, Hexbin | Relação/Correlação |
| 1 Quant + 1 Cat | Boxplot/Violin por grupo | Comparar grupos |
| Temporal | Linha, Área | Evolução no tempo |
| Correlação múltipla | Heatmap | Matriz de correlação |
😱 PARTE 1: O Gráfico do Inferno (JULIA)
Tudo que pode dar errado em um scatter plot
#| echo: true
#| output: true
#| warning: false
# Carregar dados
try
global df = CSV.read("apple_stock.csv", DataFrame)
catch e
println("⚠️ Usando dados sintéticos (arquivo não encontrado)")
global df = DataFrame(
Volume = rand(1e6:1e8, 1000),
Close = rand(50:200, 1000) .+ randn(1000) .* 10
)
end
# ❌ O PIOR GRÁFICO POSSÍVEL
scatter(df.Volume, df.Close,
title="graf", # ❌ Título não informativo
xlabel="x", # ❌ Sem contexto
ylabel="y", # ❌ Sem unidades
color=:rainbow, # ❌ Cores aleatórias
markersize=rand(1:20, length(df.Volume)), # ❌ Tamanhos variados
alpha=1.0, # ❌ Sem transparência
legend=false,
grid=false, # ❌ Sem grid
background_color=:yellow, # ❌ Fundo horrível
foreground_color=:red, # ❌ Texto ilegível
titlefontsize=8, # ❌ Fonte minúscula
tickfontsize=5, # ❌ Números ilegíveis
size=(400, 300)) # ❌ Muito pequeno
savefig("julia_helll.png")Gráfico gerado pelo Julia
😱 Mesmo Gráfico em PYTHON (rodando no Julia!)
#| echo: true
#| output: true
#| warning: false
using PyCall
# Rodar Python dentro do Julia usando PyCall
py"""
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import matplotlib
matplotlib.use('Agg') # Backend não-interativo
# Carregar dados
try:
df = pd.read_csv('apple_stock.csv')
except:
df = pd.DataFrame({
'Volume': np.random.randint(1e6, 1e8, 1000),
'Close': np.random.randint(50, 200, 1000) + np.random.randn(1000) * 10
})
# ❌ O PIOR GRÁFICO POSSÍVEL
fig, ax = plt.subplots(figsize=(4, 3), dpi=50)
colors = plt.cm.rainbow(np.linspace(0, 1, len(df)))
sizes = np.random.randint(1, 20, size=len(df))
ax.scatter(df['Volume'], df['Close'], c=colors, s=sizes, alpha=1.0)
ax.set_title('graf', fontsize=8, color='red')
ax.set_xlabel('x', fontsize=5)
ax.set_ylabel('y', fontsize=5)
ax.set_facecolor('yellow')
ax.grid(False)
ax.tick_params(labelsize=5)
plt.tight_layout()
plt.savefig('python_hell.png', dpi=50)
plt.close()
"""
println("✅ Gráfico Python gerado!")Gráfico gerado pelo Python
😱 Mesmo Gráfico em R (rodando no Julia!)
#| echo: true
#| output: true
#| warning: false
Pkg.build("RCall")
current_dir = pwd()
println("📁 Diretório atual: ", current_dir)
# Rodar R dentro do Julia usando RCall
R"""
library(ggplot2)
# Carregar dados
tryCatch({
df <- read.csv('apple_stock.csv')
}, error = function(e) {
df <<- data.frame(
Volume = sample(1e6:1e8, 1000, replace = TRUE),
Close = sample(50:200, 1000, replace = TRUE) + rnorm(1000) * 10
)
})
df$color_id <- 1:nrow(df)
df$size_random <- sample(1:20, nrow(df), replace = TRUE)
# ❌ O PIOR GRÁFICO POSSÍVEL
p <- ggplot(df, aes(x = Volume, y = Close)) +
geom_point(aes(color = color_id, size = size_random), alpha = 1.0) +
scale_color_gradientn(colors = rainbow(100)) +
labs(title = 'graf', x = 'x', y = 'y') +
theme(
plot.background = element_rect(fill = 'yellow'),
panel.background = element_rect(fill = 'yellow'),
panel.grid = element_blank(),
axis.text = element_text(size = 5, color = 'red'),
plot.title = element_text(size = 8, color = 'red'),
legend.position = 'none'
)
ggsave('r_hell.png', p, width = 4, height = 3, dpi = 50)
"""
# Exibir a imagem gerada
try
display("image/png", read("r_hell.png"))
println("✅ Gráfico R gerado!")
catch
println("⚠️ RCall não configurado. Instale com: Pkg.build(\"RCall\")")
endGráfico gerado pelo R
💡 Comparação Visual: 3 Linguagens, 1 Gráfico Horrível
Todos igualmente ruins! 😱
✅ CORREÇÃO 1: Títulos e Labels (JULIA)
#| echo: true
#| output: true
#| warning: false
using CSV, DataFrames, Plots
# Carregar dados
try
global df = CSV.read("apple_stock.csv", DataFrame)
catch e
println("⚠️ Usando dados sintéticos (arquivo não encontrado)")
global df = DataFrame(
Volume = rand(1_000_000:100_000_000, 1000),
Close = rand(50:200, 1000) .+ randn(1000) .* 10
)
end
# ✅ MELHORIA: Título e eixos descritivos
scatter(df.Volume, df.Close,
title="AAPL - Relação entre Volume e Preço de Fechamento", # ✅
xlabel="Volume Negociado (unidades)", # ✅
ylabel="Preço de Fechamento (USD)", # ✅
color=:rainbow, # Ainda ruim, mas vamos corrigir depois
markersize=rand(1:20, length(df.Volume)),
alpha=1.0,
legend=false,
grid=false,
background_color=:yellow,
foreground_color=:red,
titlefontsize=8,
tickfontsize=5,
size=(400, 300))
savefig("julia_v1.png")Gráfico Julia - versão 1
✅ CORREÇÃO 1: Títulos e Labels (PYTHON)
#| echo: true
#| output: true
#| warning: false
using PyCall
py"""
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import matplotlib
matplotlib.use('Agg')
# Recarregar dados (Python precisa do próprio df)
try:
df = pd.read_csv('apple_stock.csv')
except:
df = pd.DataFrame({
'Volume': np.random.randint(1e6, 1e8, 1000),
'Close': np.random.randint(50, 200, 1000) + np.random.randn(1000) * 10
})
fig, ax = plt.subplots(figsize=(4, 3), dpi=50)
colors = plt.cm.rainbow(np.linspace(0, 1, len(df)))
sizes = np.random.randint(1, 20, size=len(df))
ax.scatter(df['Volume'], df['Close'], c=colors, s=sizes, alpha=1.0)
# ✅ MELHORIAS
ax.set_title('AAPL - Relação entre Volume e Preço de Fechamento', fontsize=8, color='red')
ax.set_xlabel('Volume Negociado (unidades)', fontsize=5)
ax.set_ylabel('Preço de Fechamento (USD)', fontsize=5)
ax.set_facecolor('yellow')
ax.grid(False)
ax.tick_params(labelsize=5)
plt.tight_layout()
plt.savefig('python_v1.png', dpi=50)
plt.close()
"""
# Verificar
if isfile("python_v1.png")
println("✅ Gráfico Python v1 gerado!")
else
println("❌ Arquivo não criado")
endGráfico Python - versão 1
✅ CORREÇÃO 1: Títulos e Labels (R)
#| echo: true
#| output: true
#| warning: false
using RCall
R"""
library(ggplot2)
# Recarregar dados no R - CORREÇÃO: garantir que df sempre existe
df <- NULL
tryCatch({
df <- read.csv('apple_stock.csv')
}, error = function(e) {
df <<- data.frame(
Volume = sample(1e6:1e8, 1000, replace = TRUE),
Close = sample(50:200, 1000, replace = TRUE) + rnorm(1000) * 10
)
})
# Se df ainda for NULL, criar dados sintéticos
if (is.null(df)) {
df <- data.frame(
Volume = sample(1e6:1e8, 1000, replace = TRUE),
Close = sample(50:200, 1000, replace = TRUE) + rnorm(1000) * 10
)
}
df$color_id <- 1:nrow(df)
df$size_random <- sample(1:20, nrow(df), replace = TRUE)
p <- ggplot(df, aes(x = Volume, y = Close)) +
geom_point(aes(color = color_id, size = size_random), alpha = 1.0) +
scale_color_gradientn(colors = rainbow(100)) +
# ✅ MELHORIAS
labs(title = 'AAPL - Relação entre Volume e Preço de Fechamento',
x = 'Volume Negociado (unidades)',
y = 'Preço de Fechamento (USD)') +
theme(
plot.background = element_rect(fill = 'yellow'),
panel.background = element_rect(fill = 'yellow'),
panel.grid = element_blank(),
axis.text = element_text(size = 5, color = 'red'),
plot.title = element_text(size = 8, color = 'red'),
legend.position = 'none'
)
ggsave('r_v1.png', p, width = 4, height = 3, dpi = 50)
"""
# Verificar
if isfile("r_v1.png")
println("✅ Gráfico R v1 gerado!")
else
println("❌ Arquivo não criado")
endGráfico R - versão 1
✅ CORREÇÃO 2: Cores com Propósito (Python)
#| echo: true
#| output: true
using PyCall
py"""
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import matplotlib
matplotlib.use('Agg')
# Recarregar dados
try:
df = pd.read_csv('apple_stock.csv')
except:
df = pd.DataFrame({
'Volume': np.random.randint(1e6, 1e8, 1000),
'Close': np.random.randint(50, 200, 1000) + np.random.randn(1000) * 10
})
fig, ax = plt.subplots(figsize=(4, 3), dpi=50)
sizes = np.random.randint(1, 20, size=len(df))
# ✅ Cor consistente
ax.scatter(df['Volume'], df['Close'], c='steelblue', s=sizes, alpha=1.0)
ax.set_title('AAPL - Relação entre Volume e Preço de Fechamento',
fontsize=8, color='red')
ax.set_xlabel('Volume Negociado (unidades)', fontsize=5)
ax.set_ylabel('Preço de Fechamento (USD)', fontsize=5)
ax.set_facecolor('yellow')
ax.grid(False)
ax.tick_params(labelsize=5)
plt.tight_layout()
plt.savefig('python_v2.png', dpi=50)
plt.close()
"""
# Verificar
if isfile("python_v2.png")
println("✅ Gráfico Python v2 gerado!")
else
println("❌ Arquivo não criado")
endGráfico Python - versão 2
✅ CORREÇÃO 2: Cores com Propósito (R)
#| echo: true
#| output: true
#| warning: false
using RCall
R"""
library(ggplot2)
# Recarregar dados
tryCatch({
df <- read.csv('apple_stock.csv')
}, error = function(e) {
df <<- data.frame(
Volume = sample(1e6:1e8, 1000, replace = TRUE),
Close = sample(50:200, 1000, replace = TRUE) + rnorm(1000) * 10
)
})
df$size_random <- sample(1:20, nrow(df), replace = TRUE)
p <- ggplot(df, aes(x = Volume, y = Close)) +
# ✅ Cor consistente
geom_point(aes(size = size_random), color = 'steelblue', alpha = 1.0) +
labs(title = 'AAPL - Relação entre Volume e Preço de Fechamento',
x = 'Volume Negociado (unidades)',
y = 'Preço de Fechamento (USD)') +
theme(
plot.background = element_rect(fill = 'yellow'),
panel.background = element_rect(fill = 'yellow'),
panel.grid = element_blank(),
axis.text = element_text(size = 5, color = 'red'),
plot.title = element_text(size = 8, color = 'red'),
legend.position = 'none'
)
ggsave('r_v2.png', p, width = 4, height = 3, dpi = 50)
"""
# Verificar
if isfile("r_v2.png")
println("✅ Gráfico R v2 gerado!")
else
println("❌ Arquivo não criado")
endGráfico R - versão 2
✅ CORREÇÃO 3: Transparência e Tamanho (JULIA)
#| echo: true
#| output: true
scatter(df.Volume, df.Close,
title="AAPL - Relação entre Volume e Preço de Fechamento",
xlabel="Volume Negociado (unidades)",
ylabel="Preço de Fechamento (USD)",
color=:steelblue,
markersize=4, # ✅ Tamanho consistente
alpha=0.5, # ✅ Transparência
legend=false,
grid=false,
background_color=:yellow,
foreground_color=:red,
titlefontsize=8,
tickfontsize=5,
size=(400, 300))✅ CORREÇÃO FINAL: Gráfico Perfeito (JULIA)
#| echo: true
#| output: true
# Limpar dados
df_clean = dropmissing(df[!, [:Close, :Volume]])
df_clean = df_clean[df_clean.Volume .> 0, :]
df_sample = last(df_clean, 500)
# 🎉 GRÁFICO PERFEITO
scatter(df_sample.Volume, df_sample.Close,
title="AAPL - Relação entre Volume e Preço de Fechamento",
xlabel="Volume Negociado (unidades)",
ylabel="Preço de Fechamento (USD)",
color=:steelblue, # ✅
markersize=4, # ✅
alpha=0.6, # ✅
legend=false,
grid=true, # ✅
background_color=:white, # ✅
foreground_color=:black, # ✅
titlefontsize=14, # ✅
tickfontsize=10, # ✅
guidefontsize=12, # ✅
size=(1000, 700), # ✅
dpi=300) # ✅
savefig("julia_final.png")Gráfico Julia - Versão Final (perfeita)
✅ CORREÇÃO FINAL: Gráfico Perfeito (PYTHON)
#| echo: true
#| output: true
#| warning: false
using PyCall
py"""
import pandas as pd
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np
# Recarregar dados
try:
df = pd.read_csv('apple_stock.csv')
except:
df = pd.DataFrame({
'Volume': np.random.randint(1e6, 1e8, 1000),
'Close': np.random.randint(50, 200, 1000) + np.random.randn(1000) * 10
})
# Limpar dados
df_clean = df.dropna(subset=['Close', 'Volume'])
df_clean = df_clean[df_clean['Volume'] > 0].tail(500)
# 🎉 GRÁFICO PERFEITO
fig, ax = plt.subplots(figsize=(10, 7), dpi=300)
ax.scatter(df_clean['Volume'], df_clean['Close'],
c='steelblue', s=16, alpha=0.6)
ax.set_title('AAPL - Relação entre Volume e Preço de Fechamento', fontsize=14)
ax.set_xlabel('Volume Negociado (unidades)', fontsize=12)
ax.set_ylabel('Preço de Fechamento (USD)', fontsize=12)
ax.set_facecolor('white')
ax.grid(True, alpha=0.3)
ax.tick_params(labelsize=10)
plt.tight_layout()
plt.savefig('python_final.png', dpi=300, bbox_inches='tight')
plt.close()
"""
# Verificar
if isfile("python_final.png")
println("✅ Gráfico Python perfeito gerado!")
else
println("❌ Arquivo não criado")
endGráfico Python - Versão Final (perfeita)
✅ CORREÇÃO FINAL: Gráfico Perfeito (R)
#| echo: true
#| output: true
#| warning: false
using RCall
R"""
library(ggplot2)
# Recarregar dados
tryCatch({
df <- read.csv('apple_stock.csv')
}, error = function(e) {
df <- data.frame(
Volume = sample(1e6:1e8, 1000, replace = TRUE),
Close = sample(50:200, 1000, replace = TRUE) + rnorm(1000) * 10
)
})
# Limpar dados
df_clean <- df[!is.na(df$Volume) & !is.na(df$Close) & df$Volume > 0, ]
df_clean <- tail(df_clean, 500)
# 🎉 GRÁFICO PERFEITO
p <- ggplot(df_clean, aes(x = Volume, y = Close)) +
geom_point(color = 'steelblue', size = 2, alpha = 0.6) +
labs(title = 'AAPL - Relação entre Volume e Preço de Fechamento',
x = 'Volume Negociado (unidades)',
y = 'Preço de Fechamento (USD)') +
theme_minimal() +
theme(
plot.title = element_text(size = 14, hjust = 0.5),
axis.title = element_text(size = 12),
axis.text = element_text(size = 10),
panel.grid.major = element_line(color = 'gray80', linewidth = 0.5), # ✅ Corrigido
panel.grid.minor = element_blank(),
plot.background = element_rect(fill = 'white', color = NA),
panel.background = element_rect(fill = 'white')
)
ggsave('r_final.png', p, width = 10, height = 7, dpi = 300)
"""
# Verificar
if isfile("r_final.png")
println("✅ Gráfico R perfeito gerado!")
else
println("❌ Arquivo não criado")
endGráfico R - Versão Final (perfeita)
🎨 Checklist de Boas Práticas
✅ Título descritivo: O que, onde, quando
✅ Eixos claros: Variável + unidade
✅ Cores com propósito: Máx 3-5 cores
✅ Transparência: Para ver sobreposições
✅ Tamanho adequado: Mín 800x600px
✅ Fonte legível: Mín 10pt
✅ Grid: Facilita leitura
✅ Fundo neutro: Branco ou cinza claro
✅ Alta resolução: DPI ≥ 300
🎯 Próximos Slides
Nos próximos slides veremos:
- Regressão Linear (2 quantitativas)
- Distribuições (1 quantitativa)
- Boxplot/Violin (1 quant + 1 cat)
- Correlação (heatmap)
- Séries Temporais (temporal)
- Diagnóstico de Modelos
- Dashboard EDA Completo
Sempre comparando Julia, Python e R!
Os próximos slides têm análises completas com os 3 ambientes rodando em paralelo!
:::
🐍 Python: Regressão Linear
#| echo: true
#| output: true
#| eval: true
using PyCall
println("🐍 Regressão Python com apple_stock.csv...\n")
py"""
import pandas as pd
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import os
# Carregar arquivo apple_stock.csv
if not os.path.exists('apple_stock.csv'):
raise FileNotFoundError("❌ Arquivo 'apple_stock.csv' não encontrado!")
df = pd.read_csv('apple_stock.csv')
print(f"✅ apple_stock.csv carregado")
print(f" Colunas: {list(df.columns)}")
print(f" Linhas: {len(df)}")
# Limpar
df = df.dropna(subset=['Close', 'Volume'])
df = df[df['Volume'] > 0].tail(500)
x = df['Volume'].values
y = df['Close'].values
print(f"📈 {len(df)} registros após limpeza")
print(f" Volume: {x.min():.0f} - {x.max():.0f}")
print(f" Close: {y.min():.2f} - {y.max():.2f}\n")
# REGRESSÃO LINEAR MANUAL
n = len(x)
x_mean = np.mean(x)
y_mean = np.mean(y)
# Calcular slope (b) e intercept (a)
numerator = np.sum((x - x_mean) * (y - y_mean))
denominator = np.sum((x - x_mean) ** 2)
slope = numerator / denominator
intercept = y_mean - slope * x_mean
# R²
y_pred = slope * x + intercept
ss_res = np.sum((y - y_pred) ** 2)
ss_tot = np.sum((y - y_mean) ** 2)
r_squared = 1 - (ss_res / ss_tot)
print(f"📊 Slope: {slope:.2e}")
print(f"📊 Intercept: {intercept:.2f}")
print(f"📊 R²: {r_squared:.4f}\n")
# GRÁFICO
fig, ax = plt.subplots(figsize=(10, 7), dpi=150)
# Scatter
ax.scatter(x, y, alpha=0.6, color='steelblue', s=20, label='Dados')
# Linha de regressão
x_line = np.linspace(x.min(), x.max(), 100)
y_line = slope * x_line + intercept
ax.plot(x_line, y_line, color='red', linewidth=3, label='Regressão')
# Títulos
ax.set_title('Regressão Linear: Preço vs Volume (PYTHON)',
fontsize=14, fontweight='bold')
ax.set_xlabel('Volume Negociado', fontsize=12)
ax.set_ylabel('Preço Fechamento (USD)', fontsize=12)
ax.grid(True, alpha=0.3, linestyle='--')
ax.legend()
# Texto
text = f'y = {intercept:.2f} + {slope:.2e}×Volume\\nR² = {r_squared:.4f}'
ax.text(0.05, 0.95, text,
transform=ax.transAxes,
fontsize=11,
verticalalignment='top',
bbox=dict(boxstyle='round', facecolor='white', alpha=0.9),
color='red',
fontweight='bold')
plt.tight_layout()
plt.savefig('slide3_python_regression.png', dpi=150, bbox_inches='tight')
print("✅ slide3_python_regression.png")
plt.close()
"""
if isfile("slide3_python_regression.png")
println("\n✅ SUCESSO!")
else
println("\n❌ Falhou")
endPython - Regressão Linear
📈 R: Regressão Linear
#| echo: true
#| output: true
#| eval: true
using RCall
println("📊 Executando R com apple_stock.csv...\n")
R"""
library(ggplot2)
# Carregar arquivo apple_stock.csv
if (!file.exists('apple_stock.csv')) {
stop("❌ Arquivo 'apple_stock.csv' não encontrado!")
}
df <- read.csv('apple_stock.csv')
cat("✅ apple_stock.csv carregado\n")
cat(" Colunas:", paste(names(df), collapse=", "), "\n")
cat(" Linhas:", nrow(df), "\n")
# Limpar dados
df <- na.omit(df[, c('Close', 'Volume')])
df <- df[df$Volume > 0, ]
df <- tail(df, 500)
cat("📈", nrow(df), "registros após limpeza\n")
cat(" Volume:", format(min(df$Volume), scientific=FALSE), "-",
format(max(df$Volume), scientific=FALSE), "\n")
cat(" Close:", round(min(df$Close), 2), "-", round(max(df$Close), 2), "\n\n")
# Calcular regressão
model <- lm(Close ~ Volume, data=df)
r2 <- summary(model)$r.squared
coefs <- coef(model)
cat("📊 Slope:", format(coefs[2], scientific=TRUE), "\n")
cat("📊 Intercept:", round(coefs[1], 2), "\n")
cat("📊 R²:", round(r2, 4), "\n\n")
# CRIAR GRÁFICO
p <- ggplot(df, aes(x=Volume, y=Close)) +
geom_point(alpha=0.6, color='steelblue', size=2) +
geom_smooth(method='lm', color='red', linewidth=1.5,
se=TRUE, fill='pink', alpha=0.2) +
labs(
title='Regressão Linear: Preço vs Volume (R)',
x='Volume Negociado',
y='Preço Fechamento (USD)'
) +
annotate('text',
x=min(df$Volume) + (max(df$Volume) - min(df$Volume)) * 0.05,
y=max(df$Close) * 0.95,
label=paste0(
'y = ', round(coefs[1], 2), ' + ',
format(coefs[2], scientific=TRUE, digits=2), '×Volume\n',
'R² = ', round(r2, 4)
),
hjust=0, vjust=1,
color='red', size=4, fontface='bold',
fill='white', alpha=0.8) +
theme_minimal() +
theme(
plot.title=element_text(size=14, face='bold'),
axis.title=element_text(size=12),
panel.grid.major=element_line(color='gray90'),
panel.grid.minor=element_blank()
)
# Salvar
ggsave('slide3_r_regression.png', plot=p,
width=10, height=7, dpi=150)
cat("✅ slide3_r_regression.png\n")
"""
# Verificar
println("\n" * "="^60)
if isfile("slide3_r_regression.png")
println("✅ SUCESSO! Gráfico R gerado")
println("📂 slide3_r_regression.png")
else
println("❌ Arquivo não criado")
println("\n💡 Possíveis problemas:")
println(" 1. RCall não instalado: using Pkg; Pkg.add(\"RCall\")")
println(" 2. R não tem ggplot2: install.packages(\"ggplot2\")")
println(" 3. Erro de permissão de escrita")
endR - Regressão Linear
📊 SLIDE 5: Distribuições - Histograma vs Densidade
Dataset: Yahoo Stock Market Data - Kaggle
Tipo de variável: 1 Quantitativa → Histograma + Densidade
#| echo: true
#| output: true
#| eval: true
#| echo: true
#| output: true
using CSV, DataFrames, Plots, Statistics, Distributions
println("📊 Carregando dados de ações...\n")
# Função para calcular retornos
calc_returns(prices) = [0; diff(log.(prices)) .* 100]
# Tickers para analisar
tickers = ["AAPL", "TSLA", "SPY"]
# Dicionário para armazenar retornos
returns_dict = Dict{String, Vector{Float64}}()
# Carregar cada ticker
for ticker in tickers
# Tentar diferentes variações de nome
possible_files = [
"$(ticker).csv",
"$(lowercase(ticker)).csv",
"yahoo-stock-market-data/$(ticker).csv",
"stocks_data/$(ticker).csv"
]
found = false
for filepath in possible_files
if isfile(filepath)
try
df = CSV.read(filepath, DataFrame)
# Calcular retornos
returns = calc_returns(df.Close)[2:end]
returns_dict[ticker] = returns
println("✅ $ticker: $(length(returns)) retornos")
found = true
break
catch e
println("⚠️ Erro ao processar $filepath: $e")
end
end
end
if !found
println("❌ Não encontrado: $ticker")
end
end
println("\n📈 Total carregado: $(length(returns_dict)) ações\n")
# Criar plots individuais
plots_list = []
for ticker in tickers
if haskey(returns_dict, ticker)
returns = returns_dict[ticker]
# Cor por ticker
colors = Dict("AAPL" => :steelblue, "TSLA" => :purple, "SPY" => :coral)
color = get(colors, ticker, :blue)
p = histogram(returns, bins=50, normalize=:pdf,
alpha=0.6, label="Histogram",
title="$ticker - Retornos Diários",
xlabel="Retorno (%)", ylabel="Densidade",
color=color,
legend=:topright)
# KDE
density!(p, returns, linewidth=3, label="KDE", color=:red)
# Normal teórica
plot!(p, Normal(mean(returns), std(returns)),
linewidth=2, label="Normal Teórica",
linestyle=:dash, color=:black)
# Estatísticas
annotate!(p, minimum(returns) * 0.8, maximum(returns) * 0.8,
text("μ = $(round(mean(returns), digits=3))%\nσ = $(round(std(returns), digits=3))%",
:left, 9, :darkgray))
push!(plots_list, p)
else
# Plot vazio se não encontrou
p = plot(title="$ticker\n(Não disponível)",
legend=false, grid=false, showaxis=false)
push!(plots_list, p)
end
end
# Combinar plots
if length(plots_list) >= 3
p_final = plot(plots_list[1], plots_list[2], plots_list[3],
layout=(1, 3), size=(1500, 500))
else
p_final = plot(plots_list..., layout=(1, length(plots_list)))
end
# Salvar
savefig(p_final, "distributions_returns.png")
println("✅ Gráfico salvo: distributions_returns.png")
# Mostrar
display(p_final)
# Estatísticas resumo
println("\n" * "="^60)
println("ESTATÍSTICAS DE RETORNOS")
println("="^60)
for ticker in tickers
if haskey(returns_dict, ticker)
r = returns_dict[ticker]
println("$ticker:")
println(" Média: $(round(mean(r), digits=4))%")
println(" Std: $(round(std(r), digits=4))%")
println(" Min: $(round(minimum(r), digits=2))%")
println(" Max: $(round(maximum(r), digits=2))%")
println()
end
endGráfico de Estatística de Retornos
📊 Comparação: AAPL, TSLA, SPY
#| echo: true
#| output: true
#| eval: true
using StatsPlots, Distributions
println("📊 Criando distribuições de retornos...\n")
# Verificar quais dados foram carregados
if !@isdefined(returns_dict)
println("⚠️ Carregando dados primeiro...")
# Carregar dados
tickers = ["AAPL", "TSLA", "SPY"]
returns_dict = Dict{String, Vector{Float64}}()
for ticker in tickers
possible_files = [
"$(ticker).csv",
"$(lowercase(ticker)).csv",
"yahoo-stock-market-data/$(ticker).csv"
]
for filepath in possible_files
if isfile(filepath)
try
df = CSV.read(filepath, DataFrame)
returns = calc_returns(df.Close)[2:end]
returns_dict[ticker] = returns
println("✅ $ticker")
break
catch; end
end
end
end
end
# Função helper para criar plot seguro
function create_distribution_plot(returns_dict, ticker, color=:steelblue)
if haskey(returns_dict, ticker)
returns = returns_dict[ticker]
p = histogram(returns, bins=50, normalize=:pdf,
alpha=0.6,
title="$ticker - Retornos Diários",
xlabel="Retorno (%)",
ylabel="Densidade",
legend=:topright,
color=color,
label="Histogram")
density!(p, returns, linewidth=3, label="KDE", color=:red)
plot!(p, Normal(mean(returns), std(returns)),
linewidth=2, label="Normal Teórica",
linestyle=:dash, color=:black)
return p
else
# Plot vazio se não existir
return plot(title="$ticker\n(Dados não disponíveis)",
legend=false, grid=false, showaxis=false)
end
end
# Criar os 3 plots
p1 = create_distribution_plot(returns_dict, "AAPL", :steelblue)
p2 = create_distribution_plot(returns_dict, "TSLA", :purple)
p3 = create_distribution_plot(returns_dict, "SPY", :coral)
# Combinar em layout 1x3
p_final = plot(p1, p2, p3, layout=(1,3), size=(1500, 500))
# Salvar
savefig(p_final, "distributions_3stocks.png")
println("\n✅ Gráfico salvo: distributions_3stocks.png")
# Mostrar
display(p_final)📊 Comparação: AAPL, TSLA, SPY
🐍 Python: Distribuições
#| echo: true
#| output: true
#| eval: true
using PyCall
println("🐍 Distribuições Python (SEM scipy)...\n")
py"""
import pandas as pd
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import os
# Função KDE manual (sem scipy)
def kde_manual(data, bandwidth=None):
if bandwidth is None:
# Regra de Silverman
bandwidth = 1.06 * np.std(data) * len(data)**(-1/5)
x_range = np.linspace(data.min(), data.max(), 100)
kde_values = np.zeros_like(x_range)
for xi in x_range:
kernel = np.exp(-0.5 * ((data - xi) / bandwidth)**2)
kde_values[np.where(x_range == xi)] = kernel.sum() / (len(data) * bandwidth * np.sqrt(2 * np.pi))
return x_range, kde_values
# Função normal PDF manual (sem scipy)
def normal_pdf(x, mu, sigma):
return (1 / (sigma * np.sqrt(2 * np.pi))) * np.exp(-0.5 * ((x - mu) / sigma)**2)
# Tickers para carregar
tickers = ['AAPL', 'TSLA', 'SPY']
returns_data = {}
print("📊 Carregando dados...\n")
# Carregar cada ticker
for ticker in tickers:
possible_files = [
f'{ticker}.csv',
f'{ticker.lower()}.csv',
f'yahoo-stock-market-data/{ticker}.csv',
f'stocks_data/{ticker}.csv'
]
found = False
for filepath in possible_files:
if os.path.exists(filepath):
try:
df = pd.read_csv(filepath)
returns = np.diff(np.log(df['Close'])) * 100
returns_data[ticker] = returns
print(f"✅ {ticker}: {len(returns)} retornos")
found = True
break
except Exception as e:
print(f"⚠️ Erro: {e}")
if not found:
print(f"❌ Não encontrado: {ticker}")
print(f"\n📈 Total: {len(returns_data)} ações\n")
# Criar figura
fig, axes = plt.subplots(1, 3, figsize=(15, 5), dpi=150)
# Cores
colors = {'AAPL': 'steelblue', 'TSLA': 'purple', 'SPY': 'coral'}
for idx, ticker in enumerate(tickers):
ax = axes[idx]
if ticker in returns_data:
returns = returns_data[ticker]
color = colors.get(ticker, 'steelblue')
# Histograma
ax.hist(returns, bins=50, density=True, alpha=0.6,
label='Histogram', color=color)
# KDE manual
x_kde, y_kde = kde_manual(returns)
ax.plot(x_kde, y_kde, 'r-', linewidth=3, label='KDE')
# Normal teórica manual
mu, sigma = returns.mean(), returns.std()
x_norm = np.linspace(returns.min(), returns.max(), 100)
y_norm = normal_pdf(x_norm, mu, sigma)
ax.plot(x_norm, y_norm, 'k--', linewidth=2, label='Normal Teórica')
ax.set_title(f'{ticker} - Retornos Diários',
fontsize=14, fontweight='bold')
ax.set_xlabel('Retorno (%)', fontsize=12)
ax.set_ylabel('Densidade', fontsize=12)
ax.legend(loc='best')
ax.grid(True, alpha=0.3, linestyle='--')
# Estatísticas
stats_text = f'μ = {mu:.3f}%\\nσ = {sigma:.3f}%'
ax.text(0.05, 0.95, stats_text,
transform=ax.transAxes,
fontsize=9,
verticalalignment='top',
bbox=dict(boxstyle='round', facecolor='white', alpha=0.8))
else:
ax.text(0.5, 0.5, f'{ticker}\\n(Não disponível)',
ha='center', va='center', fontsize=12)
ax.set_title(f'{ticker}', fontsize=14)
ax.axis('off')
plt.tight_layout()
plt.savefig('slide5_python_distributions.png', dpi=150, bbox_inches='tight')
print("✅ slide5_python_distributions.png")
plt.close()
"""
if isfile("slide5_python_distributions.png")
println("\n✅ SUCESSO!")
else
println("\n❌ Falhou")
endPython: Distribuições
📈 R: Distribuições
#| echo: true
#| output: true
#| eval: true
using RCall
println("📊 Distribuições R...\n")
r_dist = raw"""
library(ggplot2)
library(patchwork)
aapl <- read.csv('AAPL.csv')
tsla <- read.csv('TSLA.csv')
spy <- read.csv('SPY.csv')
returns_aapl <- diff(log(aapl$Close)) * 100
returns_tsla <- diff(log(tsla$Close)) * 100
returns_spy <- diff(log(spy$Close)) * 100
df_returns <- data.frame(
Return = c(returns_aapl, returns_tsla, returns_spy),
Asset = c(rep('AAPL', length(returns_aapl)),
rep('TSLA', length(returns_tsla)),
rep('SPY', length(returns_spy)))
)
ggplot(df_returns, aes(x = Return, fill = Asset)) +
geom_histogram(aes(y = ..density..), bins = 50, alpha = 0.6) +
geom_density(aes(color = Asset), linewidth = 1.5) +
facet_wrap(~Asset, scales = 'free') +
labs(title = 'Distribuição de Retornos',
x = 'Retorno (%)',
y = 'Densidade') +
theme_minimal() +
theme(plot.title = element_text(size = 14),
axis.title = element_text(size = 12))
ggsave('slide5_r_distributions.png', width = 15, height = 5, dpi = 300)
"""
println(r_dist)
# Executar o código R
R"""
library(ggplot2)
library(patchwork)
aapl <- read.csv('AAPL.csv')
tsla <- read.csv('TSLA.csv')
spy <- read.csv('SPY.csv')
returns_aapl <- diff(log(aapl$Close)) * 100
returns_tsla <- diff(log(tsla$Close)) * 100
returns_spy <- diff(log(spy$Close)) * 100
df_returns <- data.frame(
Return = c(returns_aapl, returns_tsla, returns_spy),
Asset = c(rep('AAPL', length(returns_aapl)),
rep('TSLA', length(returns_tsla)),
rep('SPY', length(returns_spy)))
)
ggplot(df_returns, aes(x = Return, fill = Asset)) +
geom_histogram(aes(y = ..density..), bins = 50, alpha = 0.6) +
geom_density(aes(color = Asset), linewidth = 1.5) +
facet_wrap(~Asset, scales = 'free') +
labs(title = 'Distribuição de Retornos',
x = 'Retorno (%)',
y = 'Densidade') +
theme_minimal() +
theme(plot.title = element_text(size = 14),
axis.title = element_text(size = 12))
ggsave('slide5_r_distributions.png', width = 15, height = 5, dpi = 300)
"""
if isfile("slide5_r_distributions.png")
println("✅ SUCESSO!")
else
println("❌ Falhou")
endR: Distribuições
📊 Estatísticas Descritivas
#| echo: true
#| output: true
using StatsBase
println("=== Estatísticas de Retornos ===\n")
println("AAPL:")
println(" Média: $(round(mean(returns_aapl), digits=3))%")
println(" Std: $(round(std(returns_aapl), digits=3))%")
println(" Assimetria: $(round(skewness(returns_aapl), digits=3))")
println(" Curtose: $(round(kurtosis(returns_aapl), digits=3))")
println("\nTSLA:")
println(" Média: $(round(mean(returns_tsla), digits=3))%")
println(" Std: $(round(std(returns_tsla), digits=3))%")
println(" Assimetria: $(round(skewness(returns_tsla), digits=3))")
println(" Curtose: $(round(kurtosis(returns_tsla), digits=3))")
println("\nSPY:")
println(" Média: $(round(mean(returns_spy), digits=3))%")
println(" Std: $(round(std(returns_spy), digits=3))%")
println(" Assimetria: $(round(skewness(returns_spy), digits=3))")
println(" Curtose: $(round(kurtosis(returns_spy), digits=3))")📚 COMPARAÇÃO: Julia vs Python vs R
#| echo: true
#| output: true
comparison = DataFrame(
Critério = [
"Performance (Velocidade)",
"Facilidade - Básico",
"Facilidade - Avançado",
"Documentação",
"Comunidade",
"Integração Científica",
"Interatividade",
"Curva de Aprendizado",
"Melhor para"
],
Julia = [
"⭐⭐⭐⭐⭐ (5/5)",
"⭐⭐⭐⭐ (4/5)",
"⭐⭐⭐ (3/5)",
"⭐⭐⭐ (3/5)",
"⭐⭐⭐ (3/5)",
"⭐⭐⭐⭐⭐ (5/5)",
"⭐⭐⭐⭐ (4/5)",
"Média",
"Computação intensiva, performance"
],
Python = [
"⭐⭐⭐ (3/5)",
"⭐⭐⭐⭐⭐ (5/5)",
"⭐⭐⭐⭐ (4/5)",
"⭐⭐⭐⭐⭐ (5/5)",
"⭐⭐⭐⭐⭐ (5/5)",
"⭐⭐⭐⭐ (4/5)",
"⭐⭐⭐⭐⭐ (5/5)",
"Fácil",
"Prototipagem rápida, ML/DL"
],
R = [
"⭐⭐ (2/5)",
"⭐⭐⭐ (3/5)",
"⭐⭐⭐⭐⭐ (5/5)",
"⭐⭐⭐⭐⭐ (5/5)",
"⭐⭐⭐⭐ (4/5)",
"⭐⭐⭐⭐⭐ (5/5)",
"⭐⭐⭐ (3/5)",
"Média-Alta",
"Estatística acadêmica, EDA"
]
)
println(comparison)✅ CHECKLIST FINAL: Boas Práticas
ANTES DE VISUALIZAR:
✅ Entender o objetivo
✅ Conhecer o público-alvo
✅ Identificar tipo de variáveis
✅ Limpar dados
✅ Calcular estatísticas
ESCOLHA DO GRÁFICO:
✅ 1 quant → Histogram/Density
✅ 1 cat → Bar
✅ 2 quant → Scatter
✅ 1q+1c → Boxplot
✅ Temporal → Line
✅ Correlação → Heatmap
DESIGN:
✅ Título descritivo
✅ Eixos com labels/unidades
✅ Cores com propósito
✅ Transparência adequada
✅ Tamanho legível (≥800px)
✅ Fonte ≥10pt
✅ Grid quando apropriado
ÉTICA:
✅ Eixos não manipulados
✅ Escalas honestas
✅ Citar fontes
✅ Transparência total
📖 RECURSOS RECOMENDADOS
Documentação:
- Julia: Plots.jl, StatsPlots.jl
- Python: Seaborn, Matplotlib
- R: ggplot2, tidyverse
Livros:
- Fundamentals of Data Visualization - Claus Wilke
- Storytelling with Data - Cole Nussbaumer Knaflic
- The Visual Display of Quantitative Information - Edward Tufte
Datasets:
- Kaggle - Milhares de datasets reais
- UCI ML Repository - Dados para ML
- data.gov - Dados governamentais abertos
🎯 PRÓXIMOS PASSOS
- Praticar com datasets reais do Kaggle
- Explorar visualizações interativas (Plotly, D3.js)
- Aprender dashboards (Pluto.jl, Streamlit, Shiny)
- Estudar visualização de redes e geoespacial
- Aplicar em projetos reais
- Compartilhar seus trabalhos!
A melhor visualização é aquela que comunica claramente sua mensagem para o público-alvo. Simplicidade > Complexidade.
Lição de Casa: 🕵 Detetive de Visualização
Missão: Encontre um gráfico real publicado online (notícia, artigo, rede social, relatório) que seja tecnicamente correto mas visualmente problemático. Analise, refaça aplicando boas práticas e justifique suas escolhas.
Parte 1: Encontrar e Documentar
Onde procurar: - Portais de notícias (G1, Folha, Estadão) - Relatórios corporativos públicos - Artigos científicos de acesso aberto - Redes sociais (Twitter/X, LinkedIn, Instagram) - Publicações governamentais
Documentação necessária: - Screenshot do gráfico original - Fonte completa (URL + data de acesso) - Contexto: onde foi publicado e para qual audiência
Parte 2: Análise Crítica
Identifique pelo menos 6 problemas de visualização:
Categorias possíveis: - Título e labels (clareza, contexto, unidades) - Escolha de cores (contraste, acessibilidade, propósito) - Tipo de gráfico (adequação aos dados) - Escalas e eixos (manipulação, truncamento) - Tamanho e proporções (legibilidade) - Elementos desnecessários (chart junk, 3D, efeitos)
Para cada problema, explique: 1. O que está errado 2. Por que isso prejudica a comunicação 3. Como deveria ser corrigido
Parte 3: Reconstrução
Refaça o gráfico aplicando boas práticas:
Requisitos mínimos: - Título descritivo com contexto completo - Eixos com labels e unidades claras - Cores apropriadas e acessíveis - Legenda clara (se aplicável) - Grid quando necessário - Tamanho adequado (mínimo 800x600px) - Fontes legíveis (mínimo 11pt) - Tipo de gráfico apropriado aos dados
Código exigido: - Forneça o código usado (Julia, Python ou R) - Dados: extraia aproximadamente ou use os originais se disponíveis - Comente as decisões de design no código
Parte 4: Justificativa
Responda em até 300 palavras:
- Quais foram suas principais mudanças e por quê?
- Que história o gráfico corrigido conta que o original obscurecia?
- Como suas escolhas melhoram a comunicação para a audiência original?
Bônus: Identifique se há tentativa de manipulação visual intencional (escalas truncadas, gráficos enganosos) e discuta as implicações éticas.
Entrega
Arquivo PDF contendo: 1. Gráfico original (screenshot + fonte) 2. Lista de problemas identificados 3. Gráfico reconstruído 4. Código comentado 5. Justificativa escrita